package luke.audio.analyzer;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

// 添加LogUtil导入
import luke.core.logging.LogUtil;

/**
 * 节拍检测器类
 * 根据输入的真实BPM生成节拍点，并计算偏移量以实现与音频的完美卡点
 * 使用简化的算法来检测音频中的节拍点，通过计算理论鼓点周围的能量来找到最佳匹配
 */
public class BeatDetector {
    // 存储频带能量信号的字段
    private List<double[]> bandEnergySignals;
    private int targetRate;
    private int hopSize;
    // 存储最佳频带索引列表（支持多个频带）
    private List<Integer> coreBandIndices = new ArrayList<>();
    
    // 添加BPMDetector实例字段
    private BPMDetector bpmDetector;
    
    // 添加audioData、sampleRate、channels、bpms和durationMs字段
    private float[] audioData;
    private int sampleRate;
    private int channels;
    private List<Double> bpms;
    // 添加durationMs作为成员变量，避免重复计算
    private int durationMs;

    /**
     * 构造函数，接受BPMDetector参数
     * 
     * @param bpmDetector BPM检测器实例
     */
    public BeatDetector(BPMDetector bpmDetector) {
        this.bpmDetector = bpmDetector;
        // 从bpmDetector中获取必要的参数
        this.audioData = bpmDetector.getAudioData().data;
        this.sampleRate = bpmDetector.getAudioData().sampleRate;
        this.channels = bpmDetector.getAudioData().channels;
        this.bpms = bpmDetector.getAllBpmCandidates();
        
        // 从bpmDetector中获取频带能量信号相关参数
        this.bandEnergySignals = bpmDetector.getBandEnergySignals();
        this.targetRate = bpmDetector.getTargetRate();
        this.hopSize = bpmDetector.getHopSize();
        this.coreBandIndices = findCoreBandIndices(bpmDetector);
        
        // 计算音频时长（毫秒），只计算一次
        this.durationMs = (int) ((long) this.audioData.length * 1000 / (this.sampleRate * this.channels));
    }

    /**
     * 根据给定的BPM和音频时长生成均匀分布的节拍点
     * 
     * @param bpm      音频的真实BPM值
     * @param duration 音频时长（毫秒）
     * @param offset   起始偏移量（毫秒）
     * @return 节拍点列表（毫秒）
     */
    public static List<Integer> generateUniformBeats(double bpm, int duration, int offset) {
        List<Integer> beats = new ArrayList<>();

        // 将BPM转换为节拍间隔（毫秒）
        // BPM = 每分钟节拍数，所以节拍间隔 = 60000ms / BPM
        double beatIntervalMs = 60000.0 / bpm;

        // 从偏移量开始生成节拍点
        double currentTime = offset;

        // 向前生成节拍点（时间大于等于0）
        List<Integer> forwardBeats = new ArrayList<>();
        while (currentTime >= 0) {
            forwardBeats.add(0, (int) Math.round(currentTime)); // 在列表开头插入，保持时间顺序
            currentTime -= beatIntervalMs;
        }

        // 将向前生成的节拍点添加到结果中
        beats.addAll(forwardBeats);

        // 重置当前时间为偏移量，向后生成节拍点（时间小于音频时长）
        currentTime = offset + beatIntervalMs;
        while (currentTime < duration) {
            beats.add((int) Math.round(currentTime));
            currentTime += beatIntervalMs;
        }

        return beats;
    }

    /**
     * 使用简化算法检测音频中的节拍点（支持多个BPM值）
     * 
     * @param bpmDetector BPM检测器实例，用于获取频带能量数据和最佳频带索引
     * @param useBandEnergySignal true表示使用频带能量信号，false表示使用原始音频数据
     * @return 对齐后的节拍结果
     */
    public AlignedBeatResult detectBeatPositions(BPMDetector bpmDetector, boolean useBandEnergySignal) {
        // 从bpmDetector中获取必要的参数
        float[] audioData = bpmDetector.getAudioData().data;
        int sampleRate = bpmDetector.getAudioData().sampleRate;
        int channels = bpmDetector.getAudioData().channels;
        List<Double> bpms = bpmDetector.getAllBpmCandidates();
        
        if (bpms == null || bpms.isEmpty()) {
            throw new IllegalArgumentException("BPM列表不能为空");
        }
        
        LogUtil.debug("BeatDetector", "detectBeatPositions", "鼓点检测器接收到的BPM集合: " + bpms);
        
        // 保存频带能量信号和其他必要参数
        List<double[]> bandEnergySignals = bpmDetector.getBandEnergySignals();
        int targetRate = bpmDetector.getTargetRate();
        int hopSize = bpmDetector.getHopSize();
        // 获取最佳频带索引列表
        List<Integer> coreBandIndices = findCoreBandIndices(bpmDetector);
        
        LogUtil.debug("BeatDetector", "detectBeatPositions", "保存频带能量信号，信号数量: " + (bandEnergySignals != null ? bandEnergySignals.size() : 0) + ", 目标采样率: " + targetRate + ", hop大小: " + hopSize + ", 核心频带索引列表: " + coreBandIndices);
        
        // 步骤1: 生成所有BPM候选值：四舍五入、±1扩展
        List<Double> allBpmCandidates = generateAllBpmCandidates(bpms);
        LogUtil.debug("BeatDetector", "detectBeatPositions", "鼓点检测器生成的BPM候选集合: " + allBpmCandidates);
        
        // 步骤2: 搜索最佳BPM和偏移量（使用最佳频带）
        BpmOffsetScoreResult bestResult = findBestBpmAndOffsetWithCoreBands(
            audioData, sampleRate, channels, allBpmCandidates);
        
        // 步骤3: 对偏移量进行矫正（使用新的基于能量变化的矫正方法）
        int correctedOffset = correctOffsetByEnergyChange(bestResult.getOffset(), bestResult.getBpm(), audioData, sampleRate, channels, useBandEnergySignal);
        BpmOffsetScoreResult correctedResult = new BpmOffsetScoreResult(bestResult.getBpm(), correctedOffset, bestResult.getScore());
        
        LogUtil.debug("BeatDetector", "detectBeatPositions", "鼓点检测器最终选择的BPM: " + String.format("%.2f", correctedResult.getBpm()) + ", 原始偏移量: " + bestResult.getOffset() + "ms, 矫正后偏移量: " + correctedResult.getOffset() + "ms");
        
        // 步骤4: 生成节拍点并返回结果
        // 计算音频时长（毫秒）
        int durationMs = (int) ((long) audioData.length * 1000 / (sampleRate * channels));
        List<Integer> alignedBeats = generateUniformBeats(correctedResult.getBpm(), durationMs, correctedResult.getOffset());
        return new AlignedBeatResult(alignedBeats, correctedResult.getBpm(), alignedBeats.size());
    }
    

    
    /**
     * 查找核心频带索引列表
     * 
     * @param bpmDetector BPM检测器实例
     * @return 核心频带索引列表
     */
    private List<Integer> findCoreBandIndices(BPMDetector bpmDetector) {
        // 首先尝试获取多个核心频带索引
        List<Integer> indices = bpmDetector.getCoreBandIndices();
        if (indices != null && !indices.isEmpty()) {
            return new ArrayList<>(indices);
        }
        
        // 如果没有多个核心频带索引，回退到单个核心频带索引
        int coreBandIndex = bpmDetector.getCoreBandIndex();
        if (coreBandIndex >= 0) {
            List<Integer> singleIndexList = new ArrayList<>();
            singleIndexList.add(coreBandIndex);
            return singleIndexList;
        }
        
        // 如果都没有，返回空列表
        return new ArrayList<>();
    }
    
    /**
     * 生成所有BPM候选值：四舍五入、±1扩展
     * 
     * @param bpms 原始BPM列表
     * @return 所有BPM候选值
     */
    private List<Double> generateAllBpmCandidates(List<Double> bpms) {
        // 步骤1: 对所有输入的BPM进行四舍五入成整数，但类型依然保留double
        List<Double> roundedBpms = new ArrayList<>();
        for (double bpm : bpms) {
            double roundedBpm = (double) Math.round(bpm);
            roundedBpms.add(roundedBpm);
        }

        LogUtil.debug("BeatDetector", "generateAllBpmCandidates", "四舍五入后的BPM: " + roundedBpms);

        // 步骤2: 在四舍五入的基础上增加±1的BPM
        List<Double> extendedBpms = new ArrayList<>();
        for (double bpm : roundedBpms) {
            // 添加原始BPM
            if (!extendedBpms.contains(bpm)) {
                extendedBpms.add(bpm);
            }
            // 添加BPM-1
            double lowerBpm = bpm - 1;
            if (lowerBpm > 60 && !extendedBpms.contains(lowerBpm)) {
                extendedBpms.add(lowerBpm);
            }
            // 添加BPM+1
            double upperBpm = bpm + 1;
            if (upperBpm <= 180 && !extendedBpms.contains(upperBpm)) {
                extendedBpms.add(upperBpm);
            }
        }

        LogUtil.debug("BeatDetector", "generateAllBpmCandidates", "扩展后的BPM候选值: " + extendedBpms);
        return extendedBpms;
    }
    
    /**
     * 寻找每个BPM的最佳偏移并确定最终结果（支持多个核心频带）
     * 
     * @param audioData  音频数据
     * @param sampleRate 采样率
     * @param channels   声道数
     * @param bpmCandidates BPM候选值
     * @return 最佳BPM和偏移结果
     */
    private BpmOffsetScoreResult findBestBpmAndOffsetWithCoreBands(
            float[] audioData, int sampleRate, int channels, 
            List<Double> bpmCandidates) {
        
        LogUtil.debug("BeatDetector", "findBestBpmAndOffsetWithCoreBands", "开始搜索最佳BPM和偏移量，候选BPM数量: " + bpmCandidates.size());
        
        // 计算音频时长（毫秒）
        int durationMs = (int) ((long) audioData.length * 1000 / (sampleRate * channels));
        List<BpmOffsetScoreResult> results = new ArrayList<>();

        // 最外层循环每个BPM
        for (double testBpm : bpmCandidates) {
            // 生成理论节拍点
            List<Integer> theoreticalBeats = generateUniformBeats(testBpm, durationMs, 0);

            // 内层调用寻找偏移的方法，该方法同时计算偏移和得分
            BpmOffsetScoreResult result = findBestOffsetByEnergyForCoreBands(
                audioData, sampleRate, channels, theoreticalBeats, testBpm);
            results.add(result);
        }

        // 选择综合评分最高的BPM和偏移量作为最终结果
        BpmOffsetScoreResult bestResult = results.get(0);
        
        // 用于保存每个BPM的评分，便于输出调试信息
        Map<Double, Double> scoreMap = new HashMap<>();
        
        for (BpmOffsetScoreResult result : results) {
            double bpm = result.getBpm();
            double score = result.getScore();
            scoreMap.put(bpm, score);
            
            // 选择评分最高的作为最终结果
            if (result.getScore() > bestResult.getScore()) {
                bestResult = result;
            }
        }
        
        // 输出所有BPM候选值及其对应的评分，便于后期分析
        LogUtil.debug("BeatDetector", "findBestBpmAndOffsetWithCoreBands", "BPM候选值分析:");
        for (Map.Entry<Double, Double> entry : scoreMap.entrySet()) {
            double bpm = entry.getKey();
            double score = entry.getValue();
            LogUtil.debug("BeatDetector", "findBestBpmAndOffsetWithCoreBands", "BPM " + String.format("%.2f", bpm) + " -> 综合评分: " + String.format("%.2f", score));
        }
        LogUtil.debug("BeatDetector", "findBestBpmAndOffsetWithCoreBands", "选择的BPM: " + String.format("%.2f", bestResult.getBpm()) + " (综合评分: " + String.format("%.2f", scoreMap.get(bestResult.getBpm())) + ")");

        return bestResult;
    }
    
    /**
     * 通过计算每个理论鼓点周围的能量找到最佳偏移量（针对多个核心频带）
     * 
     * @param audioData        音频数据
     * @param sampleRate       采样率
     * @param channels         声道数
     * @param theoreticalBeats 理论节拍点
     * @param bpm              BPM值
     * @return 最佳偏移量和得分结果
     */
    private BpmOffsetScoreResult findBestOffsetByEnergyForCoreBands(
            float[] audioData, int sampleRate, int channels, 
            List<Integer> theoreticalBeats, double bpm) {
        // 计算节拍间隔（毫秒）
        double beatIntervalMs = 60000.0 / bpm;

        LogUtil.debug("BeatDetector", "findBestOffsetByEnergyForCoreBands", "搜索BPM " + String.format("%.2f", bpm) + " 的最佳偏移量，节拍间隔: " + String.format("%.2f", beatIntervalMs) + "ms");

        // 计算音频时长（毫秒）
        int durationMs = (int) ((long) audioData.length * 1000 / (sampleRate * channels));

        // 在一个节拍间隔内搜索最佳偏移量，按10ms步进（根据规范）
        int bestOffset = 0;
        double maxEnergy = -1;

        // 按10ms步进，计算每组理论鼓点的能量
        for (int offset = 0; offset < (int) beatIntervalMs; offset += 10) {
            // 生成当前偏移量下的理论节拍点
            // 根据实际音频时长生成节拍点，而不是硬编码+2000ms
            List<Integer> currentBeats = generateUniformBeats(bpm, durationMs, offset);

            // 计算这组理论鼓点的总能量（多个核心频带内每个鼓点后方150ms窗口的能量总和）
            double totalEnergy = calculateTotalBeatEnergyForCoreBands(
                audioData, sampleRate, channels, currentBeats);

            // 更新最佳偏移量
            if (totalEnergy > maxEnergy) {
                maxEnergy = totalEnergy;
                bestOffset = offset;
            }
        }
        
        LogUtil.debug("BeatDetector", "findBestOffsetByEnergyForCoreBands", "BPM " + String.format("%.2f", bpm) + " 的最佳偏移量: " + bestOffset + "ms, 最大能量: " + String.format("%.2f", maxEnergy));

        return new BpmOffsetScoreResult(bpm, bestOffset, maxEnergy);
    }
    
    /**
     * 计算一组节拍点的总能量（针对多个核心频带）
     * 
     * @param audioData  音频数据
     * @param sampleRate 采样率
     * @param channels   声道数
     * @param beats      节拍点列表
     * @return 总能量
     */
    private double calculateTotalBeatEnergyForCoreBands(
            float[] audioData, int sampleRate, int channels, 
            List<Integer> beats) {
        double totalEnergy = 0;

        // 对每个节拍点计算其后方的能量（11.61ms窗口）
        for (int beat : beats) {
            totalEnergy += calculateBeatEnergyForCoreBands(
                audioData, sampleRate, channels, beat);
        }

        return totalEnergy;
    }
    
    /**
     * 计算节拍点位置的能量（针对多个核心频带）
     * 使用频带能量信号而不是原始音频数据
     * 
     * @param audioData  音频数据
     * @param sampleRate 采样率
     * @param channels   声道数
     * @param beatTime   节拍点时间（毫秒）
     * @return 节拍点位置的能量
     */
    private double calculateBeatEnergyForCoreBands(
            float[] audioData, int sampleRate, int channels, 
            int beatTime) {
        // 检查频带能量信号是否可用
        if (bandEnergySignals == null || bandEnergySignals.isEmpty() || coreBandIndices.isEmpty()) {
            // 如果频带能量信号不可用，回退到使用原始音频数据
            LogUtil.debug("BeatDetector", "calculateBeatEnergyForCoreBands", "频带能量信号不可用，回退到使用原始音频数据");
            return calculateBeatEnergyFromRawAudio(audioData, sampleRate, channels, beatTime);
        }
        
        double totalEnergy = 0;
        
        // 使用所有核心频带的能量信号计算能量
        for (int coreBandIndex : coreBandIndices) {
            if (coreBandIndex >= 0 && coreBandIndex < bandEnergySignals.size()) {
                double[] energySignal = bandEnergySignals.get(coreBandIndex);
                
                // 将时间转换为帧索引
                // 首先将毫秒转换为秒
                double timeInSeconds = beatTime / 1000.0;
                // 然后根据hopSize和采样率计算帧索引
                int frameIndex = (int) (timeInSeconds * targetRate / hopSize);
                
                // 确保索引在有效范围内
                if (frameIndex >= 0 && frameIndex < energySignal.length) {
                    // 累加该频带的能量值
                    totalEnergy += energySignal[frameIndex];
                }
            }
        }
        
        // 返回所有核心频带的平均能量
        return coreBandIndices.size() > 0 ? totalEnergy / coreBandIndices.size() : 0;
    }
    
    /**
     * 从原始音频数据计算节拍点能量（回退方法）
     * 
     * @param audioData  音频数据
     * @param sampleRate 采样率
     * @param channels   声道数
     * @param beatTime   节拍点时间（毫秒）
     * @return 节拍点位置的能量
     */
    private double calculateBeatEnergyFromRawAudio(
            float[] audioData, int sampleRate, int channels, 
            int beatTime) {
        // 将时间转换为样本索引
        int sampleIndex = (int) ((long) beatTime * sampleRate * channels / 1000);

        // 确保索引在有效范围内
        if (sampleIndex < 0 || sampleIndex >= audioData.length) {
            return 0;
        }

        // 计算窗口大小（只计算节拍点之后的11.61ms，与频带能量信号帧大小一致）
        int windowSize = sampleRate * 1161 / 100000; // 11.61ms

        // 确保窗口在有效范围内
        int start = Math.max(0, sampleIndex);
        int end = Math.min(audioData.length, sampleIndex + windowSize);

        // 计算窗口内的能量
        double energy = 0;
        for (int i = start; i < end; i++) {
            energy += audioData[i] * audioData[i];
        }

        return energy;
    }
    
    /**
     * 根据能量变化对偏移量进行矫正（可选择使用频带能量信号或原始音频数据）
     * 1. 按照当前偏移向前步进(i-=10)，生成当前偏移下的理论节拍点，计算这组理论鼓点在指定信号中的总能量，
     *    并一直向前直到0，记录每一次步进这些鼓点的能量和与对应的步进的偏移
     * 2. 找到刚刚记录的鼓点能量和的最低值a
     * 3. 用还没有步进的偏移点(就是第2步输出的哪个offset点m)的能量减去他，m-a=c
     * 4. 然后遍历找到(m-x)<0.85c那个位置，这个位置就是最终的偏移点
     * 
     * @param originalOffset 原始偏移量
     * @param bpm BPM值
     * @param audioData 音频数据
     * @param sampleRate 采样率
     * @param channels 声道数
     * @param useBandEnergySignal true表示使用频带能量信号，false表示使用原始音频数据
     * @return 矫正后的偏移量
     */
    private int correctOffsetByEnergyChange(int originalOffset, double bpm, float[] audioData, int sampleRate, int channels, boolean useBandEnergySignal) {
        LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "开始进行能量变化偏移量矫正，原始偏移: " + originalOffset + "ms, BPM: " + String.format("%.2f", bpm) + ", 使用频带能量信号: " + useBandEnergySignal);
        
        // 记录使用的信号类型，便于调试验证
        if (useBandEnergySignal) {
            LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "使用频带能量信号进行纠偏");
        } else {
            LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "使用原始音频数据进行纠偏");
        }
        
        // 计算节拍间隔（毫秒）
        double beatIntervalMs = 60000.0 / bpm;
        // 计算音频时长（毫秒）
        int durationMs = (int) ((long) audioData.length * 1000 / (sampleRate * channels));
        
        // 记录每个偏移量下的能量和
        List<Double> energies = new ArrayList<>();
        List<Integer> offsets = new ArrayList<>();
        
        // 从原始偏移开始向前步进，每次减少10ms，直到0
        int currentOffset = originalOffset;
        while (currentOffset >= 0) {
            // 生成当前偏移量下的理论节拍点
            List<Integer> beats = generateUniformBeats(bpm, durationMs, currentOffset);
            
            // 计算这组理论鼓点的总能量
            double totalEnergy = 0;
            if (useBandEnergySignal) {
                // 使用频带能量信号计算
                totalEnergy = calculateTotalBeatEnergyForCoreBands(audioData, sampleRate, channels, beats);
            } else {
                // 使用原始音频数据计算
                totalEnergy = calculateTotalBeatEnergyFromRawAudio(audioData, sampleRate, channels, beats);
            }
            
            // 记录能量和偏移
            energies.add(totalEnergy);
            offsets.add(currentOffset);
            
            // 步进10ms
            currentOffset -= 10;
        }
        
        // 如果没有记录任何数据，直接返回原始偏移
        if (energies.isEmpty()) {
            LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "没有记录到能量数据，返回原始偏移量: " + originalOffset + "ms");
            return originalOffset;
        }
        
        // 找到能量的最低值
        double minEnergy = energies.get(0);
        for (double energy : energies) {
            if (energy < minEnergy) {
                minEnergy = energy;
            }
        }
        
        // 获取原始偏移点的能量（第一个记录的值）
        double originalEnergy = energies.get(0);
        
        // 计算差值c = m - a
        double energyDiff = originalEnergy - minEnergy;
        
        LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "能量分析 - 原始偏移能量: " + String.format("%.2f", originalEnergy) + 
                      ", 最低能量: " + String.format("%.2f", minEnergy) + ", 能量差值: " + String.format("%.2f", energyDiff));
        
        // 如果能量差值为0，说明没有能量变化，直接返回原始偏移
        if (energyDiff <= 0) {
            LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "能量差值为0，返回原始偏移量: " + originalOffset + "ms");
            return originalOffset;
        }
        
        // 遍历找到(m-x) < 0.5c的位置
        double threshold = 0.5 * energyDiff;
        LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "能量变化阈值: " + String.format("%.2f", threshold));
        
        // 从第二个点开始查找（第一个点是原始偏移点）
        for (int i = 1; i < energies.size(); i++) {
            double currentEnergyDiff = originalEnergy - energies.get(i);
            LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "偏移 " + offsets.get(i) + "ms, 能量差值: " + String.format("%.2f", currentEnergyDiff));
            
            if (currentEnergyDiff >= threshold) {
                // 使用瞬态低点之后的位置作为最终偏移点
                int correctedOffset = (i + 1 < offsets.size()) ? offsets.get(i + 1) : offsets.get(i);
                LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "找到矫正偏移量: " + correctedOffset + "ms");
                return correctedOffset;
            }
        }
        
        // 如果没有找到满足条件的位置，返回原始偏移
        LogUtil.debug("BeatDetector", "correctOffsetByEnergyChange", "未找到满足条件的位置，返回原始偏移量: " + originalOffset + "ms");
        return originalOffset;
    }
    
    /**
     * 计算一组节拍点的总能量（使用原始音频数据）
     * 
     * @param audioData  音频数据
     * @param sampleRate 采样率
     * @param channels   声道数
     * @param beats      节拍点列表
     * @return 总能量
     */
    private double calculateTotalBeatEnergyFromRawAudio(
            float[] audioData, int sampleRate, int channels, 
            List<Integer> beats) {
        double totalEnergy = 0;

        // 对每个节拍点计算其后方的能量（11.61ms窗口）
        for (int beat : beats) {
            totalEnergy += calculateBeatEnergyFromRawAudio(
                audioData, sampleRate, channels, beat);
        }

        return totalEnergy;
    }
    
    /**
     * BPM偏移量和得分结果类
     * 存储BPM、偏移量和得分的容器类
     */
    public static class BpmOffsetScoreResult {
        private final double bpm;
        private final int offset;
        private final double score;

        public BpmOffsetScoreResult(double bpm, int offset, double score) {
            this.bpm = bpm;
            this.offset = offset;
            this.score = score;
        }

        public double getBpm() {
            return bpm;
        }

        public int getOffset() {
            return offset;
        }
        
        public double getScore() {
            return score;
        }
    }
}

